// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2023, The Linux Foundation. All rights reserved.
 */
#include <linux/init.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/moduleparam.h>
#include <linux/ioctl.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/rbtree.h>
#include <linux/ktime.h>
#include <linux/string.h>
#include <linux/mutex.h>
#include <linux/time.h>
#include <linux/hrtimer.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>

#define GPIO_BASE 0x0300B000
#define GPIO_DAT_OFFSET(n) ((n)*0x0024 + 0x10)

static uint32_t ws2812_pin = 0;
static volatile uint32_t *ws2812_gpio_port;
static volatile uint32_t ws2812_gpio_bit;
static volatile uint32_t ws2812_set_val = 0;
static volatile uint32_t ws2812_reset_val = 0;

DEFINE_SPINLOCK(lock);

// ws2812 reset
static void ws2812_rst(void)
{
	*ws2812_gpio_port &= ~ws2812_gpio_bit;
	udelay(200);// RES low voltage time, Above 50µs
}

static void ws2812_Write_24Bits(uint32_t grb)
{
	uint8_t i;
	for (i = 0; i < 24; i++)
	{
		if (grb & 0x800000)
		{
			// loop for delay about 700ns
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			// loop for delay about 600ns
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
		}
		else
		{
			// loop for delay about 200ns
			*ws2812_gpio_port = ws2812_set_val;
			*ws2812_gpio_port = ws2812_set_val;
			// loop for delay about 800ns
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
			*ws2812_gpio_port = ws2812_reset_val;
		}
		grb <<= 1;
	}
}

static void ws2812_write_array(uint32_t *rgb, uint32_t cnt)
{
	uint32_t i = 0;
	unsigned long flags;

	for (i = 0; i < cnt; i++)
	{
		// rgb -> grb
		rgb[i] = (((rgb[i] >> 16) & 0xff) << 8) | (((rgb[i] >> 8) & 0xff) << 16) | ((rgb[i]) & 0xff);
	}

	spin_lock_irqsave(&lock, flags);
	ws2812_set_val = *ws2812_gpio_port | ws2812_gpio_bit;
	ws2812_reset_val = *ws2812_gpio_port & (~ws2812_gpio_bit);
	ws2812_rst();
	for (i = 0; i < cnt; i++)
	{
		ws2812_Write_24Bits(rgb[i]);
	}
	spin_unlock_irqrestore(&lock, flags);
}

ssize_t ws2812_read(struct file *file, char __user *user, size_t bytesize, loff_t *this_loff_t)
{
	return 0;
}

ssize_t ws2812_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
	uint32_t rgb[255];
	unsigned long ret = 0;

	if (count > 255 * 4) count = 255 * 4;
	ret = copy_from_user(&rgb[0], user_buf, count);
	if (ret < 0)
	{
		printk("copy_from_user fail!!!\n");
		return -1;
	}

	ws2812_write_array((uint32_t *)rgb, count / 4);

	return 0;
}

int ws2812_open(struct inode *inode, struct file *file)
{
	return 0;
}

int ws2812_close(struct inode *inode, struct file *file)
{
	return 0;
}

static struct file_operations ws2812_ops = {
	.owner = THIS_MODULE,
	.open = ws2812_open,
	.release = ws2812_close,
	.write = ws2812_write,
};

static struct miscdevice ws2812_misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "ws2812-led",
	.fops = &ws2812_ops,
};

static int ws2812_probe(struct platform_device *pdev)
{
	int ret;
	struct device_node *ws2812_gpio_node = pdev->dev.of_node;
	uint32_t rgb_cnt = 0;
	uint32_t rgb[255];

	of_property_read_u32(ws2812_gpio_node, "rgb_cnt", &rgb_cnt);
	if (rgb_cnt > 255)
		rgb_cnt = 255;

	of_property_read_u32_array(ws2812_gpio_node, "rgb_value", rgb, rgb_cnt);
	ws2812_pin = of_get_named_gpio(ws2812_gpio_node, "gpios", 0);
	if (!gpio_is_valid(ws2812_pin))
	{
		printk(KERN_ERR "ws2812: gpio: %d is invalid\n", ws2812_pin);
		return -ENODEV;
	}

	ws2812_gpio_port = ioremap(GPIO_BASE + GPIO_DAT_OFFSET((ws2812_pin >> 5)), 4);
	ws2812_gpio_bit = 1 << (ws2812_pin & 0x001F);

	if (gpio_request(ws2812_pin, "ws2812-gpio"))
	{
		printk(KERN_ERR "ws2812: gpio %d request failed!\n", ws2812_pin);
		gpio_free(ws2812_pin);
		return -ENODEV;
	}
	gpio_direction_output(ws2812_pin, 0);

	ret = misc_register(&ws2812_misc_dev);
	msleep(50);

	ws2812_write_array(rgb, rgb_cnt);

	return 0;
}

static int ws2812_remove(struct platform_device *pdev)
{
	misc_deregister(&ws2812_misc_dev);
	gpio_free(ws2812_pin);

	return 0;
}

static const struct of_device_id ws2812_of_match[] = {
	{.compatible = "rgb-ws2812"},
	{/* sentinel */}};

MODULE_DEVICE_TABLE(of, ws2812_of_match);

static struct platform_driver ws2812_driver = {
	.probe		= ws2812_probe,
	.remove		= ws2812_remove,
	.driver		= {
		.name	= "ws2812_ctl",
		.of_match_table = ws2812_of_match,
	},
};

module_platform_driver(ws2812_driver);

MODULE_AUTHOR("MacLodge, Alan Ma <tech@biqu3d.com>");
MODULE_DESCRIPTION("WS2812 RGB driver for Allwinner");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ws2812_ctl");
